package uk.ac.ebi.microarray.expressionprofiler.ra.fs.impl;

import java.util.List;

import javax.resource.ResourceException;

import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;

import uk.ac.ebi.microarray.expressionprofiler.ra.fs.FileSystemConnection;
import uk.ac.ebi.microarray.expressionprofiler.utils.RepeatableExecutor;
import uk.ac.ebi.microarray.expressionprofiler.utils.functional.Functor;

public class FileSystemConnectionImpl implements FileSystemConnection {

    /** Underlying physical connection instance */
    private FileSystemManagedConnection mc;

    private FileObject cwd;

    /**
     * <code>TRIES_COUNT</code> - Defines how many times we try to perform each operation.
     */
    private static final int TRIES_COUNT = 20;
    /**
     * <code>TRIES_DELAY</code> - Defines how many milliseconds (+ random seed <= TRIES_DELAY) we wait until next try.
     */
    private static final int TRIES_DELAY = 500;

    public FileSystemConnectionImpl(final FileSystemManagedConnection mc) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            this.mc = mc;
            cwd = RepeatableExecutor.execute(new Functor<FileObject>() {

                public FileObject execute() throws FileSystemException {
                    return mc.getInitialDir();
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public void associateConnection(FileSystemManagedConnection newMc) {
        synchronized (FileSystemConnectionImpl.class) {
            mc.removeConnection(this);
            newMc.addConnection(this);
            mc = newMc;
        }
    }

    public void invalidate() {
        mc = null;
    }

    public void close() throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            mc.close(this);
        }
    }

    public FileContent getFileContent(final String name) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<FileContent>() {

                public FileContent execute() throws FileSystemException {
                    FileContent content = null;
                    FileObject file = mc.getFile(cwd, name);
                    if (file != null) {
                        content = file.getContent();
                    }
                    return content;
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public FileContent getNewFileContent(final String name) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<FileContent>() {

                public FileContent execute() throws FileSystemException {
                    FileObject file = cwd.resolveFile(name);
                    file.createFile();
                    return file.getContent();
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public List<String> listDirs() throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<List<String>>() {

                public List<String> execute() throws FileSystemException {
                    return mc.listDirs(cwd);
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public List<String> listDirs(final String regexp) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<List<String>>() {

                public List<String> execute() throws FileSystemException {
                    return mc.listDirs(cwd, regexp);
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public void changeDir(String dir) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            try {
                FileObject ocwd = cwd;
                ocwd = mc.getDir(cwd, dir);
                if (ocwd == null) {
                    throw new ResourceException(dir + " not found under " + cwd.getName());
                }
                cwd = ocwd;
            } catch (FileSystemException e) {
                throw new ResourceException(e);
            }
        }
    }

    public void changeDirOrTar(String dir) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            try {
                FileObject ocwd = cwd;
                ocwd = mc.getDirOrTar(cwd, dir);
                if (ocwd == null) {
                    throw new ResourceException("No directory or tar file found for " + dir);
                }
                cwd = ocwd;
            } catch (FileSystemException e) {
                throw new ResourceException(e);
            }
        }
    }

    public boolean tryChangedir(String dir) {
        synchronized (FileSystemConnectionImpl.class) {
            try {
                changeDir(dir);
                return true;
            } catch (ResourceException e) {
                return false;
            }
        }
    }

    public List<String> listFiles() throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<List<String>>() {

                public List<String> execute() throws FileSystemException {
                    return mc.listFiles(cwd, ".*");
                }
            }, TRIES_COUNT, TRIES_DELAY);
        }
    }

    public List<String> listFiles(final String regexp) throws ResourceException {
        synchronized (FileSystemConnectionImpl.class) {
            return RepeatableExecutor.execute(new Functor<List<String>>() {

                public List<String> execute() throws FileSystemException {
                    return mc.listFiles(cwd, regexp);
                }

            }, TRIES_COUNT, TRIES_DELAY);
        }
    }
}
